# coding=utf-8

#import os
import logging as log
from bcat.gtp.serde.de.gtp_char import GtpChar
from bcat.gtp.serde.de.gtp_string_object import GtpStringObject

#todo move
class GBKCodec(object): 
    def __init__(self): 
        pass 
    def encode(self, str_text): 
        return str_text.encode(encoding='gbk') 
 
    def decode(self, bytes_text): 
        return bytes_text.decode(encoding='gbk')



class GtpStringParser(object):
    def __init__(self):
        self.gtp_char = GtpChar()


    def _init_gtp_string_object(self, gtp_str_bytes, codec):
        return GtpStringObject(gtp_str_bytes, codec)


    def to_dict(self, gtp_str_bytes, codec=GBKCodec()):
        log.info(f'to dict, src_msg_bytes : {gtp_str_bytes}')
        gtp_str_obj = self._init_gtp_string_object(gtp_str_bytes, codec)

        dst_dict = {}
        if gtp_str_obj.is_empty():
            log.warning('empty msg string')
            return dst_dict

        while True:
            ret = self._parse_pair(gtp_str_obj, dst_dict)
            if ret != 0:
                log.warning(f'_parse_pair failed')
                return None

            if not gtp_str_obj.is_end():
                if gtp_str_obj.is_pair_separator():
                    gtp_str_obj.move_to_next()
                    continue
                else:
                    log.warning(f'current index [{gtp_str_obj.current}] is [{chr(gtp_str_obj.current_byte())}], not pair separator [,]')
                    return None;

            else:
                log.info(f'to dict, dst_dict : {dst_dict}')
                return dst_dict
        
        
    def _parse_pair(self, str_obj, dst_dict):
        ret, key = self._parse_key(str_obj)
        if ret != 0:
            log.warning(f'parser key failed')
            return -1

        if not str_obj.is_end():
            if str_obj.is_kv_separator():
                str_obj.move_to_next()
            else:
                log.warning(f'after key, current index [{str_obj.current}] is [{chr(str_obj.current_byte())}], not kv_separator : =')
                return -1
        else:
            log.warning(f'after key, no bytes to parse')
            return -1

        ret, value = self._parse_value(str_obj)
        if ret != 0:
            log.warning(f'parser value failed')
            return -1

        dst_dict[key] = value
        return 0

    def _isalpha(self, char):
        return (65 <= char & char <= 90) | (97 <= char & char <= 122)
            
    def _isdigit(self, char):
        return (48 <= char & char <= 57)
            
    def _parse_key(self, str_obj):
        if str_obj.is_available():
            if self._isalpha(str_obj.current_byte()):
                current_key_start = str_obj.current
                str_obj.move_to_next()
            else:
                log.warning(f'the first letter is {str_obj.current_byte()}, not alpha')
                return -1, None
        else:
            log.warning(f'no bytes need to parse when parse first letter in key, {str_obj.current}, {str_obj.length()}')
            return -1, None
        
        for i in range(2):
            if str_obj.is_available():
                if self._isdigit(str_obj.current_byte()):
                    str_obj.move_to_next()
                else:
                    log.warning(f'the letter is {str_obj.current_byte()}, but not digit')
                    return -1, None
            else:
                log.warning(f'no bytes need to parse when parse key , {str_obj.current}, {str_obj.length()}')
                return -1, None

        str_obj.set_key_start(current_key_start)
        str_obj.set_key_end(str_obj.current)
        key = str_obj.codec.decode(str_obj.current_key())
        return 0, key 
            
    def _parse_value(self, str_obj):
        if str_obj.is_available():
            if str_obj.is_array_begin():
                return self._parse_value_array(str_obj)
            elif str_obj.is_object_begin():
                return self._parse_value_object(str_obj)
            else:
                return self._parse_value_string(str_obj)
        else:
            log.warning(f'no bytes need to parse when parse value, current : [{str_obj.current}], len : [{str_obj.length()}]')
            return -1, None
            
    def _parse_value_array(self, str_obj):
        current_value_start = str_obj.current

        if str_obj.is_available():
            if str_obj.is_array_begin():
                str_obj.move_to_next()
            else:
                log.warning(f'_parse_value_array: current is [{str_obj.current_byte()}], but not array beginning char ')
                return -1, None
        else:
            log.warning(f'no bytes need to parse when parse array value, current : [{str_obj.current}], len : [{str_obj.length()}]')
            return -1, None
        
        sub_dst_list = []
        #handle values 'xx,[xxx,xxx],{x=y}]'
        while True:
            ret, element_value = self._parse_value(str_obj)
            if ret != 0:
                log.warning(f'parse value failed when parsed array')
                return -1, None
            sub_dst_list.append(element_value)
            log.debug(f'_parse_value_array: sub dst_list : {sub_dst_list}')

            if not str_obj.is_end():
                if str_obj.is_pair_separator():
                    str_obj.move_to_next()
                elif str_obj.is_array_end():
                    str_obj.move_to_next()
                    break;
                else:
                    log.warning(f'current index [{str_obj.current}] is not pair separator or array ending char')
                    return -1, None;
            else:
                log.warning(f'_parse_value_array: after parse, need array ending char')
                return -1, None
        
        if not sub_dst_list:
            log.warning(f'_parse_value_array: empty array value')
            return -1, None

        str_obj.set_value_start(current_value_start)
        str_obj.set_value_end(str_obj.current)
        return 0, sub_dst_list

         
    def _parse_value_object(self, str_obj):
        current_value_start = str_obj.current

        if str_obj.is_available():
            if str_obj.is_object_begin():
                str_obj.move_to_next()
            else:
                log.warning(f'_parse_value_object: current is [{str_obj.current_byte()}], but not object beginning char ')
                return -1, None
        else:
            log.warning(f'no bytes need to parse when parse objetc value, current : [{str_obj.current}], len : [{str_obj.length()}]')
            return -1, None
        
        sub_dst_dict = {}
        while True:
            ret = self._parse_pair(str_obj, sub_dst_dict)
            if ret != 0:
                log.warning(f'_parse_pair failed')
                return -1, None
            log.debug(f'_parse_value_object: sub dst_dict : {sub_dst_dict}')

            if not str_obj.is_end():
                if str_obj.is_pair_separator():
                    str_obj.move_to_next()
                    continue
                elif str_obj.is_object_end():
                    str_obj.move_to_next()
                    break;
                else:
                    log.warning(f'current index [{str_obj.current_byte()}] is not pair separator or object ending char')
                    return -1, None;
            else:
                log.warning(f'_parse_value_object: after parser pair, need object ending char')
                return -1, None
        
        if not sub_dst_dict:
            log.warning(f'_parse_value_object: empty object value')
            return -1, None

        str_obj.set_value_start(current_value_start)
        str_obj.set_value_end(str_obj.current)
        return 0, sub_dst_dict


        return dst_dict


    def _parse_value_string(self, str_obj):
        current_value_start = str_obj.current

        while( str_obj.is_available()):
            if self.gtp_char.is_string_end_char(str_obj.current_byte()):
                log.debug(f'is_string_end_char [{str_obj.current_byte()}]')
                break

            if not self.gtp_char.is_slash_char(str_obj.current_byte()):
                if self.gtp_char.is_string_illegal_char(str_obj.current_byte()):
                    log.warning(f'is_string_illegal_char [{str_obj.current_byte()}]')
                    return -1,None
            else:
                str_obj.move_to_next()
                if str_obj.is_end():
                    log.warning(f'ending after slash')
                    return -1,None

                if self.gtp_char.is_not_escape_char(str_obj.current_byte()):
                    log.warning(f'normal letter [{str_obj.current_byte()}] after slash')
                    return -1,None
                
            str_obj.move_to_next()

        str_obj.set_value_start(current_value_start)
        str_obj.set_value_end(str_obj.current)

        if not len(str_obj.current_value()) :
            log.warning(f'empty string value')
            return -1,None


        value = self._escape_and_decode_value(str_obj.current_value(), str_obj.codec)
        if value is None:
            log.warning(f'escaped and decoded value failed')
            return -1,None

        log.debug(f'after escaped and decoded , dst: {value}, type: {type(value)}')
        return 0, value 

    def _escape_and_decode_value(self, bytes_value, codec):

        log.debug(f'before escaped and decoded , src: {bytes_value}, type: {type(bytes_value)}')
        value = bytearray(bytes_value)
        new_value = bytearray(len(bytes_value))

        escaped_len = 0
        is_escape = False
        for i in range(len(bytes_value)):
            if (not is_escape) & self.gtp_char.is_slash_char(bytes_value[i]):
                #print(f' char {chr(bytes_value[i])}, {i}')
                is_escape = True
                continue;
            else:
                new_value[escaped_len] = bytes_value[i]
                #print(f'escaped char {chr(new_value[j])}, {escaped_len}')
                escaped_len += 1
                is_escape = False

        escaped_value = bytes(new_value[0:escaped_len])
        log.debug(f'after escaped, dst: {escaped_value}, len: {escaped_len}')

        try:
            return codec.decode(escaped_value)
        except Exception as e:
            log.error(f'decode data failed , not gbk codec; data: {escaped_value}, error : str(e)')
            return None
